package com.fc.framework.dao;

import java.io.Serializable;
import java.lang.reflect.Method;
import java.lang.reflect.ParameterizedType;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;

import javax.annotation.Resource;

import org.apache.commons.lang3.StringUtils;
import org.hibernate.Query;
import org.hibernate.Session;
import org.hibernate.SessionFactory;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.util.CollectionUtils;

import com.fc.framework.annotation.FilterOperation;
import com.fc.framework.annotation.FilterOperation.Operation;
import com.fc.framework.filter.AbstractFilter;
import com.fc.tms.dao.DataPage;

public abstract class HibernateBaseDAO<T extends Object, ID extends Serializable> {

    private static final Logger logger = LoggerFactory.getLogger(HibernateBaseDAO.class);

    @Resource
    private SessionFactory sessionFactory;

    public void setSessionFactory(SessionFactory sessionFacotry) {
        this.sessionFactory = sessionFacotry;
    }

    public Session getSession() {
        return sessionFactory.getCurrentSession();
    }

    public Query createQuery(String queryString) {
        return this.getSession().createQuery(queryString);
    }

    @SuppressWarnings("rawtypes")
    public Query createQuery(String queryString, Map<String, Object> paramMap) {
        Query query = this.getSession().createQuery(queryString);
        for (Entry<String, Object> e : paramMap.entrySet()) {
            if (e instanceof Collection) {
                query.setParameterList(e.getKey(), (Collection) e.getValue());
            } else {
                query.setParameter(e.getKey(), e.getValue());
            }
        }
        return query;
    }

    @SuppressWarnings("unchecked")
    public Class<T> getEntityClass() {
        ParameterizedType pt = (ParameterizedType) this.getClass().getGenericSuperclass();
        return (Class<T>) pt.getActualTypeArguments()[0];
    }

    protected String getEntityName() {
        return getEntityClass().getName();
    }

    protected String getEntitySimpleName() {
        return getEntityClass().getSimpleName();
    }

    @SuppressWarnings("unchecked")
    public T get(ID id) {
        return (T) this.getSession().get(getEntityClass(), id);
    }

    @SuppressWarnings("unchecked")
    public List<T> findAll() {
        return this.getSession().createQuery("FROM " + this.getEntitySimpleName()).list();
    }

    protected Query getQuery(String hql, AbstractFilter filter) {
        QueryParam qp = this.builtQueryParam(filter);

        Query query = this.getSession().createQuery(hql + qp.getWhereSql() + qp.getOrderBySql());

        setParameter(qp.getParams(), query);

        return query;
    }

    @SuppressWarnings({ "unchecked" })
    public List<T> find(AbstractFilter filter) {
        QueryParam qp = this.builtQueryParam(filter);

        String hql = qp.getWhereSql() + qp.getOrderBySql();
        Query query = this.getSession().createQuery(hql.toString());

        setParameter(qp.getParams(), query);

        return query.list();
    }

    @SuppressWarnings({ "unchecked" })
    public DataPage<T> find(AbstractFilter filter, Integer pageIndex, Integer pageSize) {
        Map<String, String> orderMap = filter.orderBy();
        // 默认按id倒序
        if (orderMap.isEmpty()) {
            orderMap.put("id", "desc");
        }

        QueryParam qp = this.builtQueryParam(filter);

        String hql = qp.getWhereSql() + qp.getOrderBySql();
        String countHql = "SELECT COUNT(o.id)" + qp.getWhereSql();

        Query query = this.getSession().createQuery(hql.toString());
        Query countQuery = this.getSession().createQuery(countHql.toString());

        setParameter(qp.getParams(), query, countQuery);

        if (pageSize > 0) {
            query.setFirstResult((pageIndex - 1) * pageSize);
            query.setMaxResults(pageSize);
        }
        List<T> dataList = query.list();

        Integer rowCount = ((Long) countQuery.uniqueResult()).intValue();

        return new DataPage<T>(rowCount, pageIndex, pageSize, dataList);
    }

    protected void setParameter(Map<String, Object> paramMap, Query... querys) {
        for (java.util.Map.Entry<String, Object> e : paramMap.entrySet()) {
            Object v = e.getValue();
            for (Query q : querys) {
                if (v instanceof Collection) {
                    q.setParameterList(e.getKey(), (Collection<?>) v);
                } else {
                    q.setParameter(e.getKey(), v);
                }
            }
        }
    }

    protected void setParameter(List<Object> params, Query... querys) {
        for (int i = 0; i < params.size(); i++) {
            for (Query q : querys) {
                Object v = params.get(i);
                q.setParameter(i, v);
            }
        }
    }

    @SuppressWarnings("rawtypes")
    protected QueryParam builtQueryParam(AbstractFilter filter) {
        StringBuilder whereHql = new StringBuilder();
        Map<String, Object> values = new HashMap<String, Object>();

        Class clazz = filter.getClass();
        Method methods[] = clazz.getDeclaredMethods();
        try {
            for (Method m : methods) {
                String methodName = m.getName();
                if (methodName.startsWith("get")) {
                    Object obj = m.invoke(filter, new Object[] {});
                    if (obj != null && obj.toString().length() > 0) {
                        if (obj instanceof Collection && CollectionUtils.isEmpty((Collection) obj)) {
                            continue;
                        }

                        String fileName = methodName.substring(3, 4).toLowerCase() + m.getName().substring(4);

                        FilterOperation operation = clazz.getDeclaredField(fileName).getAnnotation(
                            FilterOperation.class);
                        if (operation == null) {
                            whereHql.append(" and o.").append(fileName).append(" = :").append(fileName);
                        } else if (operation.ignore()) {
                            continue;
                        } else {
                            String hql = operation.hql();
                            if (hql == null) {
                                whereHql.append(" and o.").append(fileName).append(" = :").append(fileName);
                            } else {
                                if (StringUtils.isNotBlank(hql)) {
                                    whereHql.append(" and ").append(hql);
                                }
                            }
                        }

                        if (operation != null && Operation.LIKE == operation.operation()) {
                            values.put(fileName, "%" + obj.toString() + "%");
                        } else {
                            values.put(fileName, obj);
                        }
                    }
                }
            }
        } catch (Exception e) {
            logger.error("Find by filter error", e);
        }

        StringBuilder hqlBuilder = new StringBuilder(" FROM ").append(this.getEntitySimpleName()).append(" o ");
        if (whereHql.length() > 0) {
            hqlBuilder.append(" WHERE 1=1 ").append(whereHql);
        }

        StringBuilder orderByBuilder = new StringBuilder();
        Map<String, String> orderMap = filter.orderBy();
        if (!orderMap.isEmpty()) {
            orderByBuilder.append(" ORDER BY ");
            for (Entry<String, String> e : orderMap.entrySet()) {
                orderByBuilder.append(" o." + e.getKey()).append(" ").append(e.getValue()).append(",");
            }
        }

        return new QueryParam(hqlBuilder.toString(), orderByBuilder.length() > 0 ? orderByBuilder.substring(0,
            orderByBuilder.length() - 1) : orderByBuilder.toString(), values);
    }

    public void save(T t) {
        this.getSession().save(t);
    }

    public void saveOrUpdate(T t) {
        this.getSession().saveOrUpdate(t);
    }

    public void update(T t) {
        this.getSession().update(t);
    }

    public void remove(ID id) {
        T t = this.get(id);
        if (t != null)
            this.getSession().delete(t);
    }

    protected static class QueryParam {

        String whereSql;

        String orderBySql;

        Map<String, Object> params;

        public QueryParam(String whereSql, String orderBySql, Map<String, Object> values) {
            this.whereSql = whereSql;
            this.orderBySql = orderBySql;
            this.params = values;
        }

        public String getOrderBySql() {
            return orderBySql;
        }

        public void setOrderBySql(String orderBySql) {
            this.orderBySql = orderBySql;
        }

        public String getWhereSql() {
            return whereSql;
        }

        public void setWhereSql(String whereSql) {
            this.whereSql = whereSql;
        }

        public Map<String, Object> getParams() {
            return params;
        }

        public void setParams(Map<String, Object> params) {
            this.params = params;
        }
    }
}